/**
 * 四元数 - L5
 * @author lonphy
 * @version 0.1
 */
(function(L5) {
	"use strict";

	function initConstructor() {
		/**
		 * 四元数构造
		 * @constructor
		 */
		L5.Quaternion = function() {
			// w = 0, x=y=z=0
			this.content = new Float32Array([1, 0, 0, 0]);
		};
	}
	function initMethods() {
		var $fn = L5.Quaternion.prototype;
		/**
		 * 克隆
		 * @returns {L5.Quaternion}
		 */
		$fn.clone = function(){
			var ret = new L5.Quaternion();
			ret.w = this.w;
			ret.x = this.x;
			ret.y = this.y;
			ret.z = this.z;
			return ret;
		};
		/**
		 * 置为单位四元数
		 * @returns {L5.Quaternion}
		 */
		$fn.identity = function() {
			this.content[0] = 1;
			this.content[1] = this.content[2] = this.content[3] = 0;
			return this;
		};
		/**
		 * 指定X轴角位移
		 * @param theta {Number} 角度
		 * @returns {L5.Quaternion}
		 */
		$fn.rotateX = function(theta) {
			var half = theta/2;
			this.w = Math.cos(half);
			this.x = Math.sin(half);
			this.y = this.z = 0;
			return this;
		};
		/**
		 * 指定Y轴角位移
		 * @param theta {Number} 角度
		 * @returns {L5.Quaternion}
		 */
		$fn.rotateY = function(theta) {
			var half = theta/2;
			this.w = Math.cos(half);
			this.y = Math.sin(half);
			this.x = this.z = 0;
			return this;
		};
		/**
		 * 指定Z轴角位移
		 * @param theta {Number} 角度
		 * @returns {L5.Quaternion}
		 */
		$fn.rotateZ = function(theta) {
			var half = theta/2;
			this.w = Math.cos(half);
			this.z = Math.sin(half);
			this.y = this.z = 0;
			return this;
		};
		/**
		 * 绕指定轴旋转
		 * @param vec3 {L5.FVec3|L5.IVec3} 旋转轴向量，必须标准化
		 * @param theta {Number} 角度
		 * @returns {L5.Quaternion}
		 */
		$fn.rotateAxis = function(vec3, theta) {
			// 先将向量标准化
			vec3.normalize();
			// 计算半角和sin值
			var half = theta/2;
			var sin = Math.sin(half);
			this.w = Math.cos(half);
			this.x = vec3.x * sin;
			this.y = vec3.y * sin;
			this.z = vec3.z * sin;
			return this;
		};
		/**
		 * 从欧拉角转换为四元数[物体-惯性]
		 * @param euler {L5.Euler} 目标欧拉角
		 * @returns {L5.Quaternion}
		 */
		$fn.setToRotateObjectToInertia = function(euler) {
			var sp = Math.sin(euler.pitch/2),
				sb = Math.sin(euler.bank/2),
				sh = Math.sin(euler.heading/2),
				cp = Math.cos(euler.pitch/2),
				cb = Math.cos(euler.bank/2),
				ch = Math.cos(euler.heading/2);

			this.w = cp*cb*ch + sp*sb*sh;
			this.x = sp*cb*ch + sp*sb*cp;
			this.y = sh*cb*cp - sp*ch*sb;
			this.z = cp*sb*ch - sp*sh*cb;
			return this;
		};
		/**
		 * 从欧拉角转换为四元数[惯性-物体]
		 * @param euler {L5.Euler} 目标欧拉角
		 * @returns {L5.Quaternion}
		 */
		$fn.setToRotateInertiaToObject = function(euler) {
			var sp = Math.sin(euler.pitch/2),
				sb = Math.sin(euler.bank/2),
				sh = Math.sin(euler.heading/2),
				cp = Math.cos(euler.pitch/2),
				cb = Math.cos(euler.bank/2),
				ch = Math.cos(euler.heading/2);

			this.w = cp*cb*ch + sp*sb*sh;
			this.x = -sp*cb*ch - sh*cp*sb;
			this.y = ch*sp*sb - sh*cb*cp;
			this.z = sh*sp*cb - ch*cp*sb;
			return this;
		};
		/**
		 * 叉乘, 从左向右
		 * @param qua {L5.Quaternion}
		 * @returns {L5.Quaternion}
		 */
		$fn.cross = function(qua) {
			var w = this.w, x = this.x, y = this.y, z = this.z;
			this.w = w*qua.w - x*qua.x - y*qua.y - z*qua.z;
			this.x = w*qua.x + x*qua.w + z*qua.y - y*qua.z;
			this.y = w*qua.y + y*qua.w + x*qua.z - z*qua.x;
			this.z = w*qua.z + z*qua.w + y*qua.x - x*qua.y;
			return this;
		};
		/**
		 * 点乘
		 * @param qua {L5.Quaternion}
		 * @returns {L5.Quaternion}
		 */
		$fn.dot = function(qua) {
			this.w *= qua.w;
			this.x *= qua.x;
			this.y *= qua.y;
			this.z *= qua.z;
			return this;
		};
		/**
		 * 球面线性插值
		 * @param qua {L5.Quaternion}
		 * @param t {Number}
		 * @returns {L5.Quaternion}
		 */
		$fn.slerp = function(qua, t) {
			if (t <= 0) {
				return this;
			}
			if (t >= 1) {
				return qua;
			}
			// 计算四元数夹角cos值
			var co = this.clone().dot(qua), k0, k1;
			var w=qua.w, x=qua.x, y=qua.y, z=qua.z;
			if (co < 0) {
				w=-w; x=-x; y=-y; z=-z; co=-co;
			}
			if (co > 0.99999) {
				k0 = 1-t;
				k1 = t;
			}
			else {
				var sinCo = Math.sqrt(1 - co*co);
				var theta = Math.atan2(sinCo, co);
				k0 = Math.sin( (1-t)*theta ) /sinCo;
				k1 = Math.sin(t * theta) /sinCo;
			}
			// 插值
			this.w = k0*this.w + k1*w;
			this.x = k0*this.x + k1*x;
			this.y = k0*this.y + k1*y;
			this.z = k0*this.z + k1*z;
			return this;
		};
		/**
		 * 正规化四元数
		 * @returns {L5.Quaternion}
		 */
		$fn.normalize = function() {
			var m = Math.sqrt(this.w*this.w + this.x*this.x + this.y*this.y + this.z*this.z);
			if (m > 0) {
				this.w /= m;
				this.x /= m;
				this.y /= m;
				this.z /= m;
			}else {
				this.identity();
			}
			return this;
		};
		/**
		 * 四元数共轭
		 * @returns {L5.Quaternion}
		 */
		$fn.conjugate = function() {
			this.x = -this.x;
			this.y = -this.y;
			this.z = -this.z;
			return this;
		};
		/**
		 * 四元数幂
		 * @param exp {Number}
		 * @returns {L5.Quaternion}
		 */
		$fn.pow = function(exp) {
			if (Math.abs(this.w) > 0.99999) {
				return this;
			}
			var alpha = Math.acos(this.w);
			var alpha1 = alpha*exp;
			var ito = Math.sin(alpha1)/Math.sin(alpha);

			this.w = Math.cos(alpha1);
			this.x *= ito;
			this.y *= ito;
			this.z *= ito;
			return this;
		};
		/**
		 * 提取旋转角度【弧度】
		 * @returns {Number}
		 */
		$fn.getRotateAngle = function() {
			return L5.Math.safeAcos(this.w) * 2;
		};
		/**
		 * 提取旋转轴
		 * @returns {L5.FVec3}
		 */
		$fn.getRotateAxis = function() {
			var a = 1 - this.w*this.w;
			if (a <= 0) {
				return new L5.FVec3(1, 0, 0);
			}
			a = 1 / Math.sqrt(a);
			return new L5.FVec3(this.x*a, this.y*a, this.z*a);
		};
		/**
		 * 打印
		 */
		$fn.print = function() {
			console.log(this.toString());
		};
		/**
		 * toString复写
		 * @returns {string}
		 */
		$fn.toString = function() {
			var out = 'Quaternion[';
			out += this.content[0].toFixed(8, 10) + ', ';
			out += this.content[1].toFixed(8, 10) + ', ';
			out += this.content[2].toFixed(8, 10) + ', ';
			out += this.content[3].toFixed(8, 10) + ']';
			return out;
		};
		/**
		 * 转为数组
		 * @returns {Float32Array}
		 */
		$fn.toArray = function() {
			return this.content;
		};
	}
	function setupAlias() {
		var $fn = L5.Quaternion.prototype,
			indexOf0Accessor = {
				get: function () { return this.content[0]; },
				set: function (val) { this.content[0] = val; }
			},
			indexOf1Accessor = {
				get: function () { return this.content[1]; },
				set: function (val) { this.content[1] = val; }
			},
			indexOf2Accessor = {
				get: function () { return this.content[2]; },
				set: function (val) { this.content[2] = val; }
			},
			indexOf3Accessor = {
				get: function () { return this.content[3]; },
				set: function (val) { this.content[3] = val; }
			};
		// 设置wxyz访问器
		L5.define(true, $fn, 'w', indexOf0Accessor);
		L5.define(true, $fn, 'x', indexOf1Accessor);
		L5.define(true, $fn, 'y', indexOf2Accessor);
		L5.define(true, $fn, 'z', indexOf3Accessor);
	}
	initConstructor();
	initMethods();
	setupAlias();
})(window.L5);

